/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /******************************************************************************
 * Name: dog_plugin_manager.cpp
 *
 * Purpose: plugin manager implementation
 *
 * Developer:
 *   wen.gu , 2020-08-19
 *
 * TODO:
 *
 ******************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#define LOG_TAG "PLGM"
#include "dog_plugin_manager.h"

#include <map>


#include "dog_plugin.h"
#include "dog_shared_library.h"

#include "dog_log.h"

namespace dog
{

/******************************************************************************
 **    MACROS
 ******************************************************************************/

/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/
struct PluginHandle
{
    void* dlHd = nullptr;
    std::string name;
};

using PluginHandleMap = std::map<Plugin*, PluginHandle>;


class PluginManager::impl
{
public:
    DogErrc load_l(const std::string& name);
    bool libIsLoaded(const std::string& name);

public:
    PluginHandleMap mLibMap;
    std::mutex mLock;
};


/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/

DogErrc PluginManager::impl::load_l(const std::string& name)
{
    if (libIsLoaded(name) == false)
    {
        SharedLibHandle dlHd = SharedLibOpen(name.c_str());

        if (dlHd != INVALID_LIB_HANDLE)
        {
            pluginEntryFunc_t pluginEntry =
                (pluginEntryFunc_t)SharedLibSym(dlHd, PLUGIN_ENTRY_NAME);
            pluginHdrVerFunc_t verFunc =
                (pluginHdrVerFunc_t)SharedLibSym(dlHd, PLUGIN_VER_FUNC_NAME);

            if (pluginEntry && verFunc)
            {
                const std::string ver = verFunc();

                if (ver == PLUGIN_HEADER_VER)
                {/** todo, process the compatibility of master version */
                    Plugin* mp = pluginEntry();

                    PluginHandle mph;
                    mph.dlHd = dlHd;
                    mph.name = name;
                    mLibMap[mp] = mph;
                    LOGD("load plugin(%s, ver: %s, author: %s) success "
                        "with plugin interface ver:%s\n",
                        mp->name().c_str(), mp->version().c_str(),
                        mp->author().c_str(), PLUGIN_HEADER_VER);
                    return DogErrc::OK;
                }
                else
                {
                    LOGE("version mismatch, host(%s), plugin(%s)\n",
                        PLUGIN_HEADER_VER, ver.c_str());
                    return DogErrc::Undefined;
                }
            }
            else
            {
                LOGE("lib(%s) haven't entry func(%s, %s)\n",
                    name.c_str(), PLUGIN_ENTRY_NAME,
                    PLUGIN_VER_FUNC_NAME);

                return DogErrc::Undefined;
            }
        }
        else
        {
            LOGE("load lib(%s) failed(%s)\n", name.c_str(), SharedLibError().c_str());
        }
    }
    else
    {
        LOGE("lib(%s) already loaded\n", name.c_str());
    }

    return DogErrc::InvalidStatus;
}

bool PluginManager::impl::libIsLoaded(const std::string& name)
{
    bool ret = false;
    PluginHandleMap::iterator it = mLibMap.begin();

    for (; it != mLibMap.end(); it++)
    {
        PluginHandle& mph = it->second;

        if (name == mph.name)
        {
            ret = true;
            break;
        }
    }

    return ret;
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
PluginManager::PluginManager()
    :mImpl(new impl)
{
    /**todo, something */
}

PluginManager::~PluginManager()
{
    unloadAll();
    /**todo, something */
}

//static
PluginManager& PluginManager::getInstance()
{
    static PluginManager mpm;
    return mpm;
}


DogErrc PluginManager::load(const std::string& name)
{
    //AutoLock al(mLock);
    return mImpl->load_l(name);
}

DogErrc PluginManager::loadList(std::vector<std::string>& pluginList,
    bool ignoreError/* = true*/)
{
    DogErrc ret = DogErrc::OK;
    uint32_t loadCnt = 0;
    //AutoLock al(mLock);

    for (uint32_t i = 0; i < pluginList.size(); i++)
    {
        ret = mImpl->load_l(pluginList[i]);

        if (bool(ret))
        {
            loadCnt++;
        }
        else if (ignoreError)
        {
            ret = DogErrc::OK;
        }
        else
        {
            LOGE("load plugin(%s) failed(%d, %s)\n", pluginList[i].c_str(), ret, ErrorStr(ret));
            break;
        }
    }

    LOGD("loaded plugin(%d)\n", loadCnt);

    return ret;
}


DogErrc PluginManager::unloadAll()
{
    //AutoLock al(mLock);

    PluginHandleMap::iterator it = mImpl->mLibMap.begin();

    for (; it != mImpl->mLibMap.end(); it++)
    {
        Plugin* mp = it->first;
        PluginHandle& mph = it->second;

        if (mp)
        {
            delete mp;
        }

        if (mph.dlHd)
        {
            SharedLibClose(mph.dlHd);
        }
    }

    mImpl->mLibMap.clear();

    return DogErrc::OK;
}

DogErrc PluginManager::find(const ComponentCreateParam* param, Component** comp)
{
    DogErrc ret = DogErrc::NotFound;
    //AutoLock al(mLock);
    PluginHandleMap::iterator it = mImpl->mLibMap.begin();

    for (; it != mImpl->mLibMap.end(); it++)
    {
        Plugin* mp = it->first;
        if (mp)
        {
            LOGD("===>(type: %s, plugin: %s)\n",
                param->type.c_str(), mp->name().c_str());

            ret = mp->createComponent(param, comp);

            //LOGD("===>ret(%d, %s)\n", ret.code, ret.desc);
            if (ret == DogErrc::OK)
            {
                LOGD("create component instance success, name: %s, version: %s\n",
                    (*comp)->name().c_str(), (*comp)->version().c_str());
                break;
            }
        }
    }

    return ret;
}

} /** namespace dog */
